﻿using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace tactics
{

    //This camera will allow edge-scrolling, rotation and zoom

    public class TacticsCamera
    {

        Vector3 cameraPosition;
        Vector3 cameraLookAt;
        Matrix viewMatrix;
        Matrix projMatrix;
        GraphicsDeviceManager graphics;
        int mouseX;
        int mouseY;

        const float NEAR_CLIP_PLANE = 1.0f;
        const float FAR_CLIP_PLANE = 10000.0f;
        const float ZOOM = 45.0f;
        const float ZOOM_SPEED = 0.75f;
        const float SCROLL_SPEED = 0.25f;
        const float ROTATION_SPEED = 1.5f;
        const int EDGE_SCROLL_WIDTH = 1;
        
        #region Properties

        public Matrix CameraView
        {
            get { return viewMatrix; }
        }

        public Matrix CameraProjection
        {
            get { return projMatrix; }
        }

        public Vector3 CameraPosition
        {
            get { return cameraPosition; }
        }

        public Vector3 CameraLookAt
        {
            get { return cameraLookAt; }
        }

        #endregion

        #region Public Methods

        public TacticsCamera(GraphicsDeviceManager graphics)
        {
            Set(graphics, new Vector3(-10.0f, 15.0f, -10.0f), new Vector3(25.0f, 0.0f, 25.0f));
        }

        public TacticsCamera(GraphicsDeviceManager graphics, Vector3 position, Vector3 lookAt)
        {
            Set(graphics, position, lookAt);
        }

        private void Set(GraphicsDeviceManager graphics, Vector3 position, Vector3 lookAt)
        {
            this.graphics = graphics;
            cameraPosition = position;
            cameraLookAt = lookAt;
            mouseX = Mouse.GetState().X;
            mouseY = Mouse.GetState().Y;

            viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraLookAt, Vector3.Up);

            RecalcProjection();
        }

        public void RecalcProjection()
        {
            projMatrix = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(ZOOM),
                graphics.GraphicsDevice.Viewport.AspectRatio,
                NEAR_CLIP_PLANE,
                FAR_CLIP_PLANE);
        }

        public void ChangeZoom(float speed)
        {
            Vector3 direction = cameraLookAt - cameraPosition;
            direction.Normalize();

            cameraPosition += direction * speed;
            //cameraLookAt += direction * speed;
        }

        public void ScrollCamera(float upSpeed, float sideSpeed)
        {
            Vector3 flatLook = new Vector3(cameraLookAt.X, cameraPosition.Y, cameraLookAt.Z);
            Vector3 direction = flatLook - cameraPosition;
            direction.Normalize();

            cameraPosition.X += direction.X * upSpeed;
            cameraPosition.Z += direction.Z * upSpeed;
            cameraLookAt.X += direction.X * upSpeed;
            cameraLookAt.Z += direction.Z * upSpeed;

            Vector3 up = Vector3.Up;
            Vector3.Cross(ref up, ref direction, out direction);
            direction.Normalize();

            cameraPosition.X += direction.X * sideSpeed;
            cameraPosition.Z += direction.Z * sideSpeed;
            cameraLookAt.X += direction.X * sideSpeed;
            cameraLookAt.Z += direction.Z * sideSpeed;

        }

        public void RotateCamera(float speed)
        {
            // translate position to origin, relative to where it was around lookAt
            // rotate around origin
            // translate position back to origin + lookAt

            float angle = MathHelper.ToRadians(speed);

            cameraPosition -= cameraLookAt;
            
            Matrix rotationMatrix = Matrix.CreateRotationY(angle);

            cameraPosition = Vector3.Transform(cameraPosition, rotationMatrix);

            cameraPosition += cameraLookAt;

        }

        public void UpdateCamera(GameTime gameTime, MouseState mouseState, MouseState oldMouseState, KeyboardState keyState)
        {
            
            //Zoom
            float zoomChange = 0;
            float wheelChange = (float)(oldMouseState.ScrollWheelValue - mouseState.ScrollWheelValue);

            if (wheelChange > 0)
            {
                zoomChange = -ZOOM_SPEED;
            }
            else if (wheelChange < 0)
            {
                zoomChange = ZOOM_SPEED;
            }

            //Scroll
            float up = 0.0f;
            float side = 0.0f;
            float rotate = 0.0f;

            if ((mouseState.Y <= EDGE_SCROLL_WIDTH && mouseState.Y >= 0) ||
                keyState.IsKeyDown(Keys.W))
            {
                up = SCROLL_SPEED;
            }
            else if ((mouseState.Y >= graphics.PreferredBackBufferHeight - EDGE_SCROLL_WIDTH && mouseState.Y <= graphics.PreferredBackBufferHeight) ||
                keyState.IsKeyDown(Keys.S))
            {
                up = -SCROLL_SPEED;
            }

            if ((mouseState.X <= EDGE_SCROLL_WIDTH && mouseState.X >= 0) ||
                keyState.IsKeyDown(Keys.A))
            {
                side = SCROLL_SPEED;
            }
            else if ((mouseState.X >= graphics.PreferredBackBufferWidth - EDGE_SCROLL_WIDTH && mouseState.X <= graphics.PreferredBackBufferWidth) ||
                keyState.IsKeyDown(Keys.D))
            {
                side = -SCROLL_SPEED;
            }
            
            //Rotate
            if (keyState.IsKeyDown(Keys.Z))
            {
                rotate = ROTATION_SPEED;
            }
            else if (keyState.IsKeyDown(Keys.C))
            {
                rotate = -ROTATION_SPEED;
            }

            if ((mouseState.RightButton == ButtonState.Pressed) && (oldMouseState.RightButton == ButtonState.Released))
            {
                mouseX = mouseState.X;
                mouseY = mouseState.Y;
            }

            if (mouseState.RightButton == ButtonState.Pressed)
            {
                if (mouseState.X != mouseX)
                {
                    rotate = (mouseState.X - mouseX)* 0.25f;
                }

                Mouse.SetPosition(mouseX, mouseY);
            }
            
            Vector3 oldPos = cameraPosition;
            
            if (zoomChange != 0) { ChangeZoom(zoomChange); }
            if (up != 0 || side != 0) { ScrollCamera(up, side); }
            if (rotate != 0) { RotateCamera(rotate); }

            viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraLookAt, Vector3.Up);
                    
        }

        #endregion

        #region Private Methods
        #endregion

    }
}
